package com.utils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.SystemUtils;

/**
 * The util class for creating files.
 */
public class FileUtil
{
    // ---------------------------------------------------------------------
    // Implementation methods
    // ---------------------------------------------------------------------

    /**
     * Writes a String to a file creating the file if it does not exist using
     * the "UTF-8" encoding.
     * 
     * @param pathname the pathname of the file to write.
     * @param data the content to write to the file
     */
    public static void writeStringToUTF8File( String pathname, String data )
    {
        writeStringToFile( pathname, data, "UTF-8" );
    }

    /**
     * Writes a String to a file creating the file if it does not exist using
     * the specified encoding.
     * 
     * @param pathname the pathname of the file to write.
     * @param data the content to write to the file
     * @param encoding the specified encoding.
     */
    public static void writeStringToFile( String pathname,
                                          String data,
                                          String encoding )
    {
        try
        {
            FileUtils.writeStringToFile( new File( pathname ), data, encoding );
        }
        catch( IOException ioe )
        {
            throw new RuntimeException( ioe );
        }
    }

    /**
     * Make a file from a parent pathname string and a file name, including any
     * necessary but nonexistent parent directories. Then write a string to the
     * file using the "UTF-8" encoding.
     * 
     * @param parent parent pathname
     * @param filename file name
     * @param data the content to write to the file
     */
    public static void forceWriteStringToUTF8File( String parent,
                                                   String filename,
                                                   String data )
    {
        forceWriteStringToFile( parent, filename, data, "UTF-8" );
    }

    /**
     * Make a file from a parent pathname string and a file name, including any
     * necessary but nonexistent parent directories. Then write a string to the
     * file using the specified encoding.
     * 
     * @param parent parent pathname
     * @param filename file name
     * @param data the content to write to the file
     * @param encoding the specified encoding.
     */
    public static void forceWriteStringToFile( String parent,
                                               String filename,
                                               String data,
                                               String encoding )
    {
        forceMkdir( parent );
        String pathname = parent + File.separator + filename;
        writeStringToFile( pathname, data, encoding );
    }

    /**
     * Make a directory, including any necessary but nonexistent parent
     * directories. It will do nothing if the the directory already exists.
     * 
     * @param pathname The pathname of the directory.
     */
    public static void forceMkdir( String pathname )
    {
        try
        {
            FileUtils.forceMkdir( new File( pathname ) );
        }
        catch( IOException ioe )
        {
            throw new RuntimeException( ioe );
        }

    }

    /**
     * Recursively delete a directory.
     * 
     * @param pathname The pathname of the directory.
     */
    public static void deleteDirectory( String pathname )
    {
        try
        {
            FileUtils.deleteDirectory( new File( pathname ) );
        }
        catch( IOException ioe )
        {
            throw new RuntimeException( ioe );
        }
    }

    /**
     * Clean a directory without deleting it.
     * 
     * @param pathname The pathname of the directory.
     */
    public static void cleanDirectory( String pathname )
    {
        try
        {
            FileUtils.cleanDirectory( new File( pathname ) );
        }
        catch( IOException ioe )
        {
            throw new RuntimeException( ioe );
        }
    }

    /**
     * Clean a directory with a FilenameFilter.
     * 
     * @param pathname The pathname of the directory.
     * @filter FilenameFilter,the file name filter.
     */
    public static void cleanDirectoryWithFilter( String pathname,
                                                 FilenameFilter filter )
    {
        try
        {
            File directory = new File( pathname );
            File[] files = directory.listFiles( filter );

            for( int i = 0; i < files.length; i++ )
            {
                File file = files[i];
                FileUtils.forceDelete( file );
            }
        }
        catch( IOException ioe )
        {
            throw new RuntimeException( ioe );
        }
    }

    /**
     * Copies a whole directory to a new location preserving the file dates.
     * <P>
     * This method copies the specified directory and all its child directories
     * and files to the specified destination. The destination is the new
     * location and name of the directory.
     * <p>
     * The destination directory is created if it does not exist. If the
     * destination directory did exist, then this method merges the source with
     * the destination, with the source taking precedence.
     * 
     * @param srcPathname The pathname of an existing directory to copy, must
     *            not be <code>null</code>
     * @param destPathname The pathname of the directory to place the copy in,
     *            must not be<code>null</code>
     */
    public static void copyDirectory( String srcPathname, String destPathname )
    {
        try
        {
            File srcDir = new File( srcPathname );
            File destDir = new File( destPathname );
            FileUtils.copyDirectory( srcDir, destDir );
        }
        catch( IOException ioe )
        {
            throw new RuntimeException( ioe );
        }
    }

    /**
     * Copies a file to a new location preserving the file dates.
     * <P>
     * This method copies the specified file to the specified destination. The
     * destination is the new location and name of the directory.
     * <p>
     * The destination directory is created if it does not exist. If the
     * destination directory did exist, then this method merges the source with
     * the destination, with the source taking precedence.
     * 
     * @param src The source file pathname.
     * @param dest The destination directory pathname.
     */
    public static void copyFileToDirectory( String src, String dest )
    {
        try
        {
            File srcFile = new File( src );
            File destDir = new File( dest );
            FileUtils.copyFileToDirectory( srcFile, destDir );
        }
        catch( IOException ioe )
        {
            throw new RuntimeException( ioe );
        }
    }

    /**
     * Returns the canonial pathname for a pathname.
     * 
     * @param pathname The pathname
     * @return The canonical pathname.
     * @throws RuntimeException
     */
    public static String getCanonicalPath( String pathname )
    {

        File f = new File( pathname );
        try
        {
            return f.getCanonicalPath();
        }
        catch( IOException ioe )
        {
            throw new RuntimeException( ioe );
        }
    }

    /**
     * Delete a file specified by the pathname. If file is a directory, delete
     * it and all sub-directories.
     * <p>
     * The difference between File.delete() and this method are:
     * <p>
     * <ul>
     * <li>A directory to be deleted does not have to be empty.</li>
     * <li>You get a runtime exception when a file or directory cannot be
     * deleted. (<code>java.io.File</code> methods returns a boolean)</li>
     * <li>Do nothing if the file does not exist</li>
     * 
     * @param pathname The pathname of the file or directory to delete.
     */
    public static void forceDelete( String pathname )
    {
        try
        {
            File f = new File( pathname );
            if( f.exists() )
            {
                FileUtils.forceDelete( new File( pathname ) );
            }
        }
        catch( IOException ioe )
        {
            throw new RuntimeException( ioe );
        }

    }

    /**
     * Compare the contents of two files to determine if they are equal or not.
     * <p>
     * This method checks to see if the two files are different lengths or if
     * they point to the same file, before resorting to byte-by-byte comparison
     * of the contents.
     * 
     * @param pathname1 The pathname of the first file.
     * @param pathname2 The pathname of the second file.
     * @return <code>true</code> if the content of the files are equal or they
     *         both don't exist, <code>false</code> otherwise
     */
    public static boolean contentEquals( String pathname1, String pathname2 )
    {
        File file1 = new File( pathname1 );
        File file2 = new File( pathname2 );
        try
        {
            return FileUtils.contentEquals( file1, file2 );
        }
        catch( IOException ioe )
        {
            throw new RuntimeException( ioe );
        }
    }

    /**
     * Returns the full pathname for the resource entry name.
     * 
     * @param r The root full pathname without the ending file separator.
     * @param res The resource entry name. It does not matter whether it is
     *            prefixed with <tt>/</tt>.
     * @return The full pathname for the resource entry name.
     */
    public static String getResPathname( String r, String res )
    {
        String withoutSlashPrefix = res;
        if( res.startsWith( "/" ) )
        {
            withoutSlashPrefix = res.substring( 1 );
        }
        String relPath = withoutSlashPrefix.replace( '/', File.separatorChar );
        return FilenameUtils.concat( r, relPath );
    }

    /**
     * Copies the files of a directory to within another directory.
     * <p>
     * This method copies the source directory and its contents whose file names
     * do not equals to <code>excluded</code> to a directory of the same name
     * in the specified destination directory.
     * <p>
     * The destination directory is created if it does not exist. If the
     * destination directory did exist, then this method merges the source with
     * the destination, with the source taking precedence.
     * 
     * @param srcPathname The pathname of an existing directory to copy, must
     *            not be <code>null</code>
     * @param destPathname The pathname of the directory to place the copy in,
     *            must not be<code>null</code>
     * @param The excluded file name.
     */
    public static void copyDirectory( String srcPathname,
                                      String destPathname,
                                      String excluded )
    {
        try
        {
            File srcDir = new File( srcPathname );
            File destDir = new File( destPathname );
            copyDirectory( srcDir, destDir, excluded );
        }
        catch( IOException ioe )
        {
            throw new RuntimeException( ioe );
        }
    }

    // copyDirectory( File srcDir, File destDir, String excluded ) is based on
    // source code from org.apache.commons.io.FileUtils

    /**
     * Copies the files of a directory to within another directory preserving
     * the file dates.
     * <p>
     * This method copies the source directory and its contents whose file names
     * do not equal to <code>excluded</code> to a directory of the same name
     * in the specified destination directory.
     * <p>
     * The destination directory is created if it does not exist. If the
     * destination directory did exist, then this method merges the source with
     * the destination, with the source taking precedence.
     * 
     * @param srcDir An existing directory to copy, must not be
     *            <code>null</code>
     * @param destDir The directory to place the copy in, must not be
     *            <code>null</code>
     * @param excluded The excluded file name.
     * @throws NullPointerException if source or destination is
     *             <code>null</code>
     * @throws IOException if source or destination is invalid
     * @throws IOException if an IO error occurs during copying
     */
    private static void copyDirectory( File srcDir,
                                       File destDir,
                                       String excluded ) throws IOException
    {
        if( srcDir == null )
            throw new NullPointerException( "Source must not be null" );
        if( destDir == null )
            throw new NullPointerException( "Destination must not be null" );
        if( srcDir.exists() == false )
            throw new FileNotFoundException( "Source '" + srcDir
                    + "' does not exist" );
        if( srcDir.isDirectory() == false )
            throw new IOException( "Source '" + srcDir
                    + "' exists but is not a directory" );
        if( srcDir.getCanonicalPath().equals( destDir.getCanonicalPath() ) )
            throw new IOException( "Source '" + srcDir + "' and destination '"
                    + destDir + "' are the same" );
        doCopyDirectory( srcDir, destDir, excluded );
    }

    /**
     * Internal copy directory method.
     * 
     * @param srcDir the validated source directory, must not be
     *            <code>null</code>
     * @param destDir the validated destination directory, must not be
     *            <code>null</code>
     * @param excluded The files whose pathnames contain <tt>excluded</tt>
     *            string are not copied.
     * @throws IOException if an error occurs
     */
    private static void doCopyDirectory( File srcDir,
                                         File destDir,
                                         String excluded ) throws IOException
    {
        if( destDir.exists() )
        {
            if( destDir.isDirectory() == false )
            {
                throw new IOException( "Destination '" + destDir
                        + "' exists but is not a directory" );
            }
        }
        else
        {
            if( destDir.mkdirs() == false )
            {
                throw new IOException( "Destination '" + destDir
                        + "' directory cannot be created" );
            }
        }
        if( destDir.canWrite() == false )
        {
            throw new IOException( "Destination '" + destDir
                    + "' cannot be written to" );
        }
        // recurse
        File[] files = srcDir.listFiles();

        // null if security restricted
        if( files == null )
            throw new IOException( "Failed to list contents of " + srcDir );

        for( int i = 0; i < files.length; i++ )
        {
            if( files[i].getName().equals( excluded ) )
            {
                continue;
            }
            File copiedFile = new File( destDir, files[i].getName() );

            if( files[i].isDirectory() )
            {
                doCopyDirectory( files[i], copiedFile, excluded );
            }
            else
            {
                FileUtils.copyFile( files[i], copiedFile );
            }
        }
    }

}
